module vlib.ev.fcgi.Process;

import vlib.Types;

private {
	extern(Windows) {
		int bind(int , void*, int);
	}
}

struct vProcess {
	static const MaxPathLen	= 512 ;
	alias typeof(*this) This ;
	alias typeof(this) pThis ;
	
	wchar[MaxPathLen]		dir, app , cmd ;
	PROCESS_INFORMATION	pi ;
	STARTUPINFO			si ;
	SECURITY_ATTRIBUTES	sa;
	Berkeley				soc ;
	bool					_running ;
	bool					_rebooting ;
	vAddr4				_addr ;
	int 					_id ;
	ev_timer				reboot_watcher ;
	
	static int			php_port ;
	static vProcess[]	list ;
	static ev_timer		timer_watcher ;
	static char[]		_env ;
	static Mutex		_mux ;
	static char[] toNullEndedBuffer(char[][char[]] src){
		char[] dest;
		foreach (key, value; src) {
			dest ~= key ~ '=' ~ value ~ '\0';
		}
		dest ~= "\0\0";
		return dest;
	}
		
	static void Init() {
		vFcgReq.Init ;
		
		int		size	=  _G.fcgi_max_process * This.sizeof ;
		void*	_ptr	=  cast(void*) GC.calloc( size , GC.BlkAttr.NO_MOVE | GC.BlkAttr.NO_SCAN ) ;
		list			= cast(This[]) _ptr[0 .. size] ;
		assert(list.length is _G.fcgi_max_process );
		
		scope path	= new FilePath( _G.php ) ;
		php_port		= _G.fcgi_port + 1 ;
		
		ubyte[1024 * 32] _env_tmp ;
		scope _env_bu	= new vBuffer(_env_tmp);
		foreach(char[] key, char[] val; _G.env ) {
			_env_bu(key)('=')(val)('\0');
		}
		foreach( char[] _key, _val ; Environment.get ) {
			auto _ptr	= _key in  _G.env ;
			if( _ptr !is null || _key.length is 0 ) {
				continue ;
			}
			// lg("`{}`='{}'", _key, _val);
			_env_bu(_key)('=')(_val)('\0');
		}
		_env_bu("\0\0") ;
		
		_env	= (cast(char[]) _env_bu.slice).dup ;

		foreach( int i, ref p; list) {
			toWDir( p.dir.ptr ,  path.parent);
			toWDir( p.app.ptr , path.toString);
			if( _G.php_args !is null ) {
				toWDir( p.cmd.ptr ,  _G.php_args );
			}
			p._id	= i ;
			p.reboot_watcher.data	= &p ;
			ev_timer_init(&p.reboot_watcher ,&On_Reboot, 2, 2);
			p.setup ;
			p.exec() ;
			if( ! p._running ) {
				vLib.Exit(4) ;
			}
		}
		
		ev_timer_init(&timer_watcher, &On_Timer, 2, 2);
		ev_timer_start(vLib.loop, &timer_watcher) ;
		
		_mux	= new Mutex ;
	}
	
	static void SavePid(){
		ubyte[1024] _tmp;
		scope bu	= new vBuffer(_tmp);
		foreach( int i, ref p; list) {
			bu(p.pi.dwProcessId)('\n');
		}
		ubyte[512] _tmp_file ;
		scope bu2	= new vBuffer(_tmp_file);
		bu2(vLib.app)(".pid");
		File.set( cast(char[]) bu2.slice , bu.slice );
	}
	
	static void find(ref pThis _this) {
		static int	php_i	= 0 ;
		int pos 	= 0 ;
		if( list.length > 1 ) {
			pos = list.length % php_i++ ;
		}
		while(true) {
			_mux.lock() ;
			bool	isFind	= false ;
			scope(exit) {
				_mux.unlock() ;
				if( !isFind ) {
					Thread.sleep(.3) ;
				}
			}
			foreach( int i, ref p; list) {
				if( i < pos ) {
					continue ;
				}
				if( !p._rebooting ) {
					isFind	= true ;
					_this	= &p ;
					return ;
				}
			}
			foreach( int i, ref p; list) {
				if( i >= pos ) {
					continue ;
				}
				if( !p._rebooting ) {
					isFind	= true ;
					_this	= &p ;
					return ;
				}
			}
		}
	}
	
	void setup() {
		tango.stdc.string.memset(&pi, 0, PROCESS_INFORMATION.sizeof) ;
		tango.stdc.string.memset(&si, 0, STARTUPINFO.sizeof );
		si.cb		= STARTUPINFO.sizeof;
		si.dwFlags	= STARTF_USESTDHANDLES;
		soc.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
		
		_addr._family      = AddressFamily.INET ;  
		static ubyte[4] _ipv	= [127, 0,  0 , 1];
		tango.stdc.string.memcpy(&_addr._ip[0], &_ipv[0], _ipv.length ) ;
		
		while(true) {
			if( php_port >= 65534 ) {
				vLib.Exit(3) ;
			}
			_addr._port	= vlib.util.Byte.htons( php_port ) ;
			int	ret		= bind(soc.handle, &_addr, _addr.sizeof) ;
			log!(__FILE__,__LINE__)("port:{}", php_port);
			if( ret !is -1 ) {
				break ;
			}
			php_port++;
		}
		soc.listen(32);
		
		si.hStdInput	= cast(HANDLE) soc.handle ;
		si.hStdOutput	= INVALID_HANDLE_VALUE ; 
		si.hStdError	= INVALID_HANDLE_VALUE ;
		_running		= false ;
	}
	
	void reboot() {
		if( _rebooting ) {
			return ;
		}
		_rebooting	= true ;
		ev_timer_start(vLib.loop, &reboot_watcher);
		soc.detach ;
		stop() ;
	}
	
	void exec() {
		auto ret		= CreateProcessW(&app[0], &cmd[0], null, null, true, CREATE_NO_WINDOW, _env.ptr , &dir[0], &si, &pi ) ;
		if( ret ) {
			_running	= true ;
			CloseHandle(pi.hThread);
		} else {
			// log error 
			_running	= false ;
		}
	}
	
	void stop() {
		if (_running) {
			if (TerminateProcess( pi.hProcess, cast(UINT) -1) ) {
				scope(exit) {
					CloseHandle(pi.hProcess);
					_running = false;
				}
				DWORD ret	= WaitForSingleObject(pi.hProcess, INFINITE) ;
				// WAIT_FAILED
				if( ret !is 0 ){
					// error log 
				}
			}
		}
	}
	
	static void toWDir(T)(T* ptr, char[] str) {
		tango.stdc.string.memset(ptr, 0,  MaxPathLen * T.sizeof );
		if( str is null || str.length is 0 ) {
			return ;
		}
		T[]	_str	= Utf.fromString8!(T)(str, ptr[0..MaxPathLen]);
		foreach(ref c; _str) {
			if( c is '/' )  c = '\\' ;
		}
	}
	
	static extern(C) void StopAll(){
		if( list !is null && list.length ) foreach( int i, ref p; list) {
			p.stop;
		}
	}
	
	static extern(C) void On_Timer(ev_loop_t* loop, ev_timer* w, int ev) {
		static int count	= 1 ;
		foreach( int i, ref p; list) {
			DWORD rc = WaitForSingleObject(p.pi.hProcess, 0) ;
			if( rc !is 258 ) {
				//p.stop ;
				//p.exec ;
			}
		}
	}
	
	static extern(C) void On_Reboot(ev_loop_t* loop, ev_timer* w, int ev) {
		pThis _this	= cast(pThis) w.data ;
		_this._rebooting	= false ;
		ev_timer_start(vLib.loop, &_this.reboot_watcher);
		_this.setup ;
		_this.exec;
	}
}





struct PROCESSENTRY32W {
	DWORD dwSize;
	DWORD cntUsage;
	DWORD th32ProcessID;
	DWORD th32DefaultHeapID;
	DWORD th32ModuleID;
	DWORD cntThreads;
	DWORD th32ParentProcessID;
	LONG pcPriClassBase;
	DWORD dwFlags;
	WCHAR szExeFile[MAX_PATH];
}

extern(Windows){
	HANDLE CreateToolhelp32Snapshot(DWORD, DWORD);
	BOOL Process32FirstW(HANDLE, PROCESSENTRY32W*);
	BOOL Process32NextW(HANDLE, PROCESSENTRY32W*);
	const TH32CS_SNAPPROCESS  = 0x2 ;
}

void TerminateOldProcess() {
	PROCESSENTRY32W pe32;
	pe32.dwSize	= pe32.sizeof;
	HANDLE hProcessSnap	= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0u);
	if( hProcessSnap == INVALID_HANDLE_VALUE ){
		return ;
	}
	scope(exit){
		CloseHandle(hProcessSnap);
	}
	int i	= rfind(_G.php, '/');
	if( i is 0 || i is _G.php.length ) {
		i	=  rfind(_G.php, '\\');
	}
	if( i is 0 || i is _G.php.length ) {
		return ;
	}
	char[] _php	= _G.php[i+1..$] ;
	wchar[512] tmp ;
	wchar[] __php	= Utf.toString16(_php, tmp);
	
	uint[512]	_pid ;
	int		_pid_i ;
	BOOL bMore	= Process32FirstW(hProcessSnap, &pe32);
	while(bMore){
		int	j = find(pe32.szExeFile, '\0');
		if(  pe32.szExeFile[0..j] == __php ) {
			_pid[ _pid_i++]	= pe32.th32ProcessID ;
		}                  
		bMore	=	Process32NextW(hProcessSnap, &pe32);
	}
	if( _pid_i is 0 ){
		return ;
	}
	while(_pid_i--) {
		HANDLE   _handle	=OpenProcess(PROCESS_ALL_ACCESS,TRUE,  _pid[_pid_i]);
		DWORD   _code	=0;
		TerminateProcess(_handle, _code);
		CloseHandle(_handle);
	}
	Thread.sleep(0.3) ;
}
